(function ( exports ) {

    //region OrderExpression

    /**
     * Order Expression
     * @param {string} attributeName Attribute Logical Name
     * @param {COSA.Const.OrderType} orderType Order Type
     * @prop {string} attributeName Attribute Logical Name
     * @prop {COSA.Const.OrderType} orderType
     * @memberOf COSA
     * @constructor OrderExpression
     */
    function OrderExpression ( attributeName, orderType ) {
        COSA.Xrm2011ContractsValueBase.call(this);

        this.attributeName = attributeName;
        this.orderType = orderType;
    }

    COSA.Util.deriveOverride(COSA.Xrm2011ContractsValueBase, OrderExpression, {
        type:'OrderExpression',
        validate: function () {
            (new COSA.ValueChecker(this.attributeName))
                .defined()
                .typeOf('string');

            (new COSA.ValueChecker(this.orderType))
                .defined()
                .typeOf('string');
        },
        getTemplate: function ( sc, tp ) {
            return {
                tag: tp.tag('OrderExpression'),
                nodes: [{
                    tag: tp.tag('AttributeName'),
                    val: this.attributeName
                }, {
                    tag: tp.tag('OrderType'),
                    val: this.orderType
                }]
            };
        }
    });

    //endregion OrderExpression

    //region PageInfo

    /**
     * PageInfo
     * @param {string} pagingCookie Paging cookie
     * @param {boolean} returnTotalRecordCount Return total record count
     * @prop {number} count Count
     * @prop {number} pageNumber Page number
     * @prop {string} pagingCookie Paging cookie
     * @prop {boolean} returnTotalRecordCount Return total record count
     * @memberOf COSA
     * @constructor PageInfo
     */
    function PageInfo ( pagingCookie, returnTotalRecordCount ) {
        COSA.Xrm2011ContractsValueBase.call(this, 'PageInfo');

        this.count = 0;
        this.pageNumber = 0;
        this.pagingCookie = pagingCookie;
        this.returnTotalRecordCount = typeof returnTotalRecordCount == 'undefined' ? true : returnTotalRecordCount;
    }

    COSA.Util.deriveOverride(COSA.Xrm2011ContractsValueBase, PageInfo, {
        type: 'PageInfo',
        validate: function () {
            (new COSA.ValueChecker(this.count))
                .defined()
                .int();

            (new COSA.ValueChecker(this.pageNumber))
                .defined()
                .int();

            (new COSA.ValueChecker(this.returnTotalRecordCount))
                .defined()
                .typeOf('boolean');
        },
        getTemplate: function ( sc, tp ) {
            var pagingCookieNode = { tag: tp.tag('PagingCookie') };

            if ( this.pagingCookie === null || typeof this.pagingCookie == 'undefined' ) {
                pagingCookieNode.attr = { 'i:nil':'true' };
            }
            else {
                pagingCookieNode.val = this.pagingCookie;
            }

            return [{
                tag: tp.tag('PageInfo'),
                nodes: [{
                    tag: tp.tag('Count'),
                    val: this.count
                }, {
                    tag: tp.tag('PageNumber'),
                    val: this.pageNumber
                }, pagingCookieNode, {
                    tag: tp.tag('ReturnTotalRecordCount'),
                    val: this.returnTotalRecordCount
                }]
            }];
        }
    });

    //endregion PageInfo

    //region QueryExpression

    /**
     * QueryExpression
     * @param {string} entityLogicalName Entity logical name
     * @param {COSA.ColumnSet} columnSet
     * @param {COSA.FilterExpression} criteria Filter expression
     * @prop {string} entityLogicalName Entity logical name
     * @prop {COSA.ColumnSet} columnSet Column set
     * @prop {COSA.FilterExpression} criteria Filter expression
     * @prop {boolean} distinct Distinct
     * @prop {COSA.LinkEntity[]} linkEntities Link entities
     * @prop {COSA.OrderExpression[]} orders Orders
     * @prop {boolean} noLock No lock
     * @prop {COSA.PageInfo} pageInfo Page info
     * @memberOf COSA
     * @constructor QueryExpression
     */
    function QueryExpression ( entityLogicalName, columnSet, criteria ) {
        COSA.Xrm2011ContractsValueBase.call(this);

        this.entityLogicalName = entityLogicalName;
        this.columnSet = columnSet;
        this.criteria = criteria;
        this.distinct = false;
        this.linkEntities = null;
        this.orders = null;
        this.noLock = false;
        this.topCount = null;
        this.pageInfo = new PageInfo();
    }

    COSA.Util.deriveOverride(COSA.Xrm2011ContractsValueBase, QueryExpression, {
        type: 'QueryExpression',
        requireTypeDef: true,
        validate: function () {},
        getTemplate: function ( sc, ltr ) {
            var linkEntitiesNode = { tag: ltr.tag('LinkEntities')},
                linkEntitiesSerializationResult = (new COSA.LinkEntitiesCollection(this.linkEntities)).serialize(sc),
                ordersNode = { tag: ltr.tag('Orders')},
                ordersSerializationResult = (new COSA.OrderExpressionCollection(this.orders)).serialize(sc);

            if ( linkEntitiesSerializationResult !== null ) {
                linkEntitiesNode.nodes = linkEntitiesSerializationResult;
            }

            if ( ordersSerializationResult !== null ) {
                ordersNode.nodes = ordersSerializationResult;
            }

            return [{
                tag: ltr.tag('ColumnSet'),
                nodes: this.columnSet.serialize(sc)
            }, {
                tag: ltr.tag('Criteria'),
                nodes: this.criteria.serialize(sc)
            }, {
                tag: ltr.tag('Distinct'),
                val: this.distinct
            }, {
                tag: ltr.tag('EntityName'),
                val: this.entityLogicalName
            }, linkEntitiesNode, ordersNode, {
                tag: ltr.tag('PageInfo'),
                nodes: this.pageInfo.serialize(sc)
            }, {
                tag: ltr.tag('NoLock'),
                val: this.noLock.toString()
            }];
        }
    });

    //endregion QueryExpression

    //region ConditionExpression

    /**
     * ConditionExpression
     * @param {string} attributeName Attribute logical name
     * @param {COSA.Const.ConditionOperator} operator Condition Operator
     * @param values Values
     * @prop {string} attributeName Attribute logical name
     * @prop {COSA.Const.ConditionOperator} operator Condition Operator
     * @prop values Values
     * @memberOf COSA
     * @constructor ConditionExpression
     */
    function ConditionExpression ( attributeName, operator, values ) {
        COSA.Xrm2011ContractsValueBase.call(this);

        this.attributeName = attributeName;
        this.operator = operator;
        this.values = values;
    }

    COSA.Util.deriveOverride(COSA.Xrm2011ContractsValueBase, ConditionExpression, {
        type: 'ConditionExpression',
        getTemplate: function ( sc, ltr ) {
            var valuesNode = {
                tag: ltr.tag('Values')
            };

            sc.snapshot();

            valuesNode.nodes = (new COSA.AnyTypeCollection(this.values)).serialize(sc);
            sc.appendToParent(valuesNode);

            sc.restore();

            return {
                tag: ltr.tag(this.type),
                nodes: [{
                    tag: ltr.tag('AttributeName'),
                    val: this.attributeName
                }, {
                    tag: ltr.tag('Operator'),
                    val: this.operator
                }, valuesNode, {
                    tag: ltr.tag('EntityName'),
                    attr: { 'i:nil':'true' }
                }]
            };
        }
    });

    //endregion ConditionExpression

    //region FilterExpression

    /**
     * Filter expression
     * @param {COSA.Const.FilterOperator} filterOperator Filter operator
     * @param {COSA.ConditionExpression[]} conditions Conditions
     * @param {COSA.FilterExpression[]} filters Filters
     * @prop {COSA.Const.FilterOperator} filterOperator Filter operator
     * @prop {COSA.ConditionExpression[]} conditions Conditions
     * @prop {COSA.FilterExpression[]} filters Filters
     * @memberOf COSA
     * @constructor FilterExpression
     */
    function FilterExpression ( filterOperator, conditions, filters ) {
        COSA.Xrm2011ContractsValueBase.call(this);

        this.filterOperator = typeof filterOperator != 'undefined' ? filterOperator : COSA.Const.FilterOperator.AND;
        this.conditions = conditions;
        this.filters = filters;
    }

    COSA.Util.deriveOverride(COSA.Xrm2011ContractsValueBase, FilterExpression, {
        type: 'FilterExpression',
        getTemplate: function ( sc, ltr ) {
            return [{
                tag: ltr.tag('Conditions'),
                nodes: (new COSA.ConditionsCollection(this.conditions)).serialize(sc)
            }, {
                tag: ltr.tag('FilterOperator'),
                val: this.filterOperator
            }, {
                tag: ltr.tag('Filters'),
                nodes: (new COSA.FiltersCollection(this.filters)).serialize(sc)
            }];
        },
        validate: function () { }
    });

    //endregion FilterExpression

    //region LinkEntity

    /**
     * LinkEntity
     * @param {object} initials Initial values
     * @param {COSA.ColumnSet} initials.columns Columns
     * @param {string} initials.entityAlias Entity Alias
     * @param {COSA.Const.JoinOperator} initials.joinOperator Join operator
     * @param {COSA.FilterExpression} initials.linkCriteria Link Criteria
     * @param {COSA.LinkEntity[]} initials.linkEntities Link entities
     * @param {string} initials.linkFromAttributeName Link from attribute name
     * @param {string} initials.linkToAttributeName Link to attribute name
     * @param {string} initials.linkFromEntityName Link from entity name
     * @param {string} initials.linkToEntityName Link to entity name
     * @prop {COSA.ColumnSet} columns Columns
     * @prop {string} entityAlias Entity Alias
     * @prop {COSA.Const.JoinOperator} joinOperator Join operator
     * @prop {COSA.FilterExpression} linkCriteria Link Criteria
     * @prop {COSA.LinkEntity[]} linkEntities Link entities
     * @prop {string} linkFromAttributeName Link from attribute name
     * @prop {string} linkToAttributeName Link to attribute name
     * @prop {string} linkFromEntityName Link from entity name
     * @prop {string} linkToEntityName Link to entity name
     * @memberOf COSA
     * @constructor LinkEntity
     */
    function LinkEntity ( initials ) {
        COSA.Xrm2011ContractsValueBase.call(this);

        this.columns = initials.columns;
        this.entityAlias = initials.entityAlias;
        this.joinOperator = initials.joinOperator;
        this.linkCriteria = typeof initials.linkCriteria != 'undefined' ? initials.linkCriteria : new FilterExpression();
        this.linkEntities = initials.linkEntities;
        this.linkFromAttributeName = initials.linkFromAttributeName;
        this.linkToAttributeName = initials.linkToAttributeName;
        this.linkFromEntityName = initials.linkFromEntityName;
        this.linkToEntityName = initials.linkToEntityName;
    }

    COSA.Util.deriveOverride(COSA.Xrm2011ContractsValueBase, LinkEntity, {
        type: 'LinkEntity',
        getTemplate: function ( sc, ltr ) {
            var linkEntitiesNode = { tag: ltr.tag('LinkEntities') };

            if ( Array.isArray(this.linkEntities) ) {
                linkEntitiesNode.nodes = (new COSA.LinkEntitiesCollection(this.linkEntities)).serialize(sc);
            }

            return [{
                tag: ltr.tag('Columns'),
                nodes: this.columns.serialize(sc)
            }, {
                tag: ltr.tag('EntityAlias'),
                val: this.entityAlias
            }, {
                tag: ltr.tag('JoinOperator'),
                val: this.joinOperator
            }, {
                tag: ltr.tag('LinkCriteria'),
                nodes: this.linkCriteria.serialize(sc)
            }, linkEntitiesNode, {
                tag: ltr.tag('LinkFromAttributeName'),
                val: this.linkFromAttributeName
            }, {
                tag: ltr.tag('LinkFromEntityName'),
                val: this.linkFromEntityName
            }, {
                tag: ltr.tag('LinkToAttributeName'),
                val: this.linkToAttributeName
            }, {
                tag: ltr.tag('LinkToEntityName'),
                val: this.linkToEntityName
            }];
        }
    });

    //endregion LinkEntity

    exports.OrderExpression = OrderExpression;
    exports.PageInfo = PageInfo;
    exports.LinkEntity = LinkEntity;
    exports.FilterExpression = FilterExpression;
    exports.ConditionExpression = ConditionExpression;
    exports.QueryExpression = QueryExpression;

})(COSA);
